/*-------------------------------------------------------------------------
 *
 * ts_utils.c
 *		各种支持函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/ts_utils.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <ctype.h>

#include "miscadmin.h"
#include "tsearch/ts_locale.h"
#include "tsearch/ts_utils.h"


/*
 * 给定 tsearch 配置文件的基本名称和扩展名，返回
 * 其完整路径名。基本名称假定为用户提供，
 * 并进行检查以防止路径名攻击。扩展名假定
 * 是安全的。
 *
 * 结果是一个 palloc 的字符串。
 */
char * get_tsearch_config_filename(const char *fc_basename,
							const char *fc_extension)
{
	char		fc_sharepath[MAXPGPATH];
	char	   *fc_result;

	/*
	 * 我们限制基本名称只能包含 a-z、0-9 和下划线。 这可能
	 * 过于严格，但我们不想允许访问任何
	 * tsearch_data 目录以外的内容，因此例如 '/' *必须*
	 * 被拒绝，并且在某些平台上 '\' 和 ':' 也存在风险。 允许
	 * 大写字母可能导致区分大小写和不区分大小写的文件系统之间的不兼容行为，
	 * 而非 ASCII 字符则会产生其他有趣的风险，因此总体而言，严格的政策似乎是最好的。
	 */
	if (strspn(fc_basename, "abcdefghijklmnopqrstuvwxyz0123456789_") != strlen(fc_basename))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("invalid text search configuration file name \"%s\"",
						fc_basename)));

	get_share_path(my_exec_path, fc_sharepath);
	fc_result = palloc(MAXPGPATH);
	snprintf(fc_result, MAXPGPATH, "%s/tsearch_data/%s.%s",
			 fc_sharepath, fc_basename, fc_extension);

	return fc_result;
}

/*
 * 读取停用词文件。 每个单词都会通过 'wordop'
 * 函数进行处理（如果提供）。 wordop 可以修改输入内容，
 * 也可以 palloc 一个新版本。
 */
void readstoplist(const char *fc_fname, StopList *fc_s, char *(*fc_wordop) (const char *))
{
	char	  **fc_stop = NULL;

	fc_s->len = 0;
	if (fc_fname && *fc_fname)
	{
		char	   *fc_filename = get_tsearch_config_filename(fc_fname, "stop");
		tsearch_readline_state fc_trst;
		char	   *fc_line;
		int			fc_reallen = 0;

		if (!tsearch_readline_begin(&fc_trst, fc_filename))
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("could not open stop-word file \"%s\": %m",
							fc_filename)));

		while ((fc_line = tsearch_readline(&fc_trst)) != NULL)
		{
			char	   *fc_pbuf = fc_line;

			/* 修剪尾随空格 */
			while (*fc_pbuf && !t_isspace(fc_pbuf))
				fc_pbuf += pg_mblen(fc_pbuf);
			*fc_pbuf = '\0';

			/* 跳过空行 */
			if (*fc_line == '\0')
			{
				pfree(fc_line);
				continue;
			}

			if (fc_s->len >= fc_reallen)
			{
				if (fc_reallen == 0)
				{
					fc_reallen = 64;
					fc_stop = (char **) palloc(sizeof(char *) * fc_reallen);
				}
				else
				{
					fc_reallen *= 2;
					fc_stop = (char **) repalloc((void *) fc_stop,
											  sizeof(char *) * fc_reallen);
				}
			}

			if (fc_wordop)
			{
				fc_stop[fc_s->len] = fc_wordop(fc_line);
				if (fc_stop[fc_s->len] != fc_line)
					pfree(fc_line);
			}
			else
				fc_stop[fc_s->len] = fc_line;

			(fc_s->len)++;
		}

		tsearch_readline_end(&fc_trst);
		pfree(fc_filename);
	}

	fc_s->stop = fc_stop;

	/* 排序以允许二进制搜索 */
	if (fc_s->stop && fc_s->len > 0)
		qsort(fc_s->stop, fc_s->len, sizeof(char *), pg_qsort_strcmp);
}

bool searchstoplist(StopList *fc_s, char *fc_key)
{
	return (fc_s->stop && fc_s->len > 0 &&
			bsearch(&fc_key, fc_s->stop, fc_s->len,
					sizeof(char *), pg_qsort_strcmp));
}
